unit uComplexNumber;

interface

uses uNumber;

type

  TComplexNumber = class (TNumber)
  private
    class var imaginaryCharacter: char;
    class var imaginaryCharacterBeforeNumber: boolean;

    var FRealPart: TNumberBase;
    var FImaginaryPart: TNumberBase;

    function  GetRealPart: TNumberBase;
    procedure SetRealPart(AValue: TNumberBase);

    function  GetImaginaryPart: TNumberBase;
    procedure SetImaginaryPart(AValue: TNumberBase);

    procedure FromExponentialString(const AString: String);

  public
    class procedure SetFormat(AImaginaryCharacter: char; AImaginaryCharacterBeforeNumber: boolean);

    constructor Create; overload;
    constructor Create(ARealPart, AImaginaryPart: TNumberBase); overload;

    function  CreateNumberOfSameType: TNumber; override;

    function  Abs: TNumberBase; override;
    procedure Add(ANumber: TNumber); override;
    procedure BecomeAdditiveIdentity; override;
    procedure BecomeMultiplicativeIdentity; override;
    procedure CopyFrom(ANumber: TNumber); override;
    procedure Clear; override;
    procedure ChangeSign; override;
    procedure DivideBy(ANumber: TNumber); override;
    procedure FromString(const AString: String); override;
    procedure Invert; override;
    function  IsAdditiveIdentity:boolean; override;
    function  IsMultiplicativeIdentity:boolean; override;
    procedure MultiplyBy(ANumber: TNumber); override;
    procedure Random; override;
    procedure Subtract(ANumber: TNumber); override;
    function  ToExponentialString(const AFormat: String = DEFAULT_NUMBER_FORMAT): String;
    function  ToString(const AFormat: String = DEFAULT_NUMBER_FORMAT): String; override;

    property  RealPart: TNumberBase read GetRealPart write SetRealPart;
    property  ImaginaryPart: TNumberBase read GetImaginaryPart write SetImaginaryPart;
  end;

implementation

uses SysUtils, uNumberExceptions;

{ TComplexNumber }

function TComplexNumber.Abs: TNumberBase;
begin
  if not self.Valid then
  begin
    raise ECalculatingWithInvalidNumbers.Create('Calculating absolute value of an invalid number');
  end;

  result := sqrt( sqr(FRealPart) + sqr(FImaginaryPart) );
end;

procedure TComplexNumber.Add(ANumber: TNumber);
begin
  if (not self.Valid) or (not ANumber.IsValid) then
  begin
    raise ECalculatingWithInvalidNumbers.Create('Calculating with invalid numbers');
  end;

  if ANumber is TComplexNumber then
  begin
    FRealPart := FRealPart + TComplexNumber(ANumber).FRealPart;
    FImaginaryPart := FImaginaryPart + TComplexNumber(ANumber).ImaginaryPart;
  end
  else begin
    raise EIncompatibleNumberTypes.Create(GetIncompatibleNumberTypesMsg(ANumber));
  end;
end;

procedure TComplexNumber.BecomeAdditiveIdentity;
begin
  FRealPart := 0;
  FImaginaryPart := 0;
  Valid := true;
end;

procedure TComplexNumber.BecomeMultiplicativeIdentity;
begin
  FRealPart := 1;
  FImaginaryPart := 0;
  Valid := true;
end;

procedure TComplexNumber.ChangeSign;
begin
  if not self.Valid then
  begin
    raise ECalculatingWithInvalidNumbers.Create('Changing sign of an invalid number');
  end;

  FRealPart := -1 * FRealPart;
  FImaginaryPart := -1 * FImaginaryPart;
end;

procedure TComplexNumber.Clear;
begin
  FRealPart := 0;
  FImaginaryPart := 0;
  Valid := true;
end;

procedure TComplexNumber.CopyFrom(ANumber: TNumber);
begin
  if not self.Valid then
  begin
    raise ECalculatingWithInvalidNumbers.Create('Copying an invalid number');
  end;

  if ANumber is TComplexNumber then
  begin
    FRealPart := TComplexNumber(ANumber).RealPart;
    FImaginaryPart := TComplexNumber(ANumber).FImaginaryPart;
  end
  else begin
    raise EIncompatibleNumberTypes.Create(GetIncompatibleNumberTypesMsg(ANumber));
  end;
end;

constructor TComplexNumber.Create(ARealPart, AImaginaryPart: TNumberBase);
begin
  inherited Create;
  FRealPart := ARealPart;
  FImaginaryPart := AImaginaryPart;
end;

function TComplexNumber.CreateNumberOfSameType: TNumber;
begin
  Result := TComplexNumber.Create;
end;

constructor TComplexNumber.Create;
begin
  inherited Create;
  FRealPart := 0;
  FImaginaryPart := 0;
end;

procedure TComplexNumber.DivideBy(ANumber: TNumber);
var
  dividentRealPart: TNumberBase;
  dividentImaginaryPart: TNumberBase;
  divisorRealPart: TNumberBase;
  divisorImaginaryPart: TNumberBase;
begin
  if (not self.Valid) or (not ANumber.IsValid) then
  begin
    raise ECalculatingWithInvalidNumbers.Create('Calculating with invalid numbers');
  end;

  if ANumber is TComplexNumber then
  begin
    dividentRealPart := FRealPart;
    dividentImaginaryPart := FImaginaryPart;
    divisorRealPart := TComplexNumber(ANumber).RealPart;
    divisorImaginaryPart := TComplexNumber(ANumber).ImaginaryPart;

    FRealPart := ( dividentRealPart * divisorRealPart - dividentImaginaryPart * (-1*divisorImaginaryPart) ) / ( sqr(divisorRealPart) + sqr(divisorImaginaryPart) );
    FImaginaryPart:=( dividentRealPart * (-1*divisorImaginaryPart) + dividentImaginaryPart * divisorRealPart ) / ( sqr(divisorRealPart) + sqr(divisorImaginaryPart) );
  end
  else begin
    raise EIncompatibleNumberTypes.Create(GetIncompatibleNumberTypesMsg(ANumber));
  end;
end;

procedure TComplexNumber.FromExponentialString(const AString: String);
begin
  //TODO: implement me
end;

procedure TComplexNumber.FromString(const AString: String);
var
  firstPart, secondPart: String;
  splitPosition: Word;

  procedure ParseNumberPart(ANumberPart: String);
  var i: word;
  begin
    if ANumberPart <> '' then
    begin
      i := pos(imaginaryCharacter, ANumberPart);
      if i = 0 then
      begin
        FRealPart := StrToFloat(ANumberPart)
      end
      else begin
        Delete(ANumberPart, i, 1);

        if (ANumberPart = '') or (ANumberPart = '+') then
          FImaginaryPart := 1
        else if ANumberPart = '-' then
          FImaginaryPart := -1
        else
          FImaginaryPart := StrToFloat(ANumberPart);
      end;
    end;
  end;

begin
  if pos('e^', AString) <> 0 then
  begin
    FromExponentialString(AString);
    exit;
  end;

  Clear;

  //separate real and imaginary part
  splitPosition := LastDelimiter('+-', AString);

  if ( pos(imaginaryCharacter, AString) <> 0 ) and ( splitPosition > 1 ) then
  begin
    firstPart := Copy(AString, 1, splitPosition-1);
    secondPart := Copy(AString, splitPosition, length(AString));
  end
  else begin
    firstPart := AString;
    secondPart := '';
  end;

  try
    ParseNumberPart(firstPart);
    ParseNumberPart(secondPart);
  except
    self.Valid := false;
    raise EInvalidStringValue.CreateFmt('"%s" is not a valid TComplexNumber value',[AString]);
  end;

  //5, +5, -5, j, +j, -j, j2, +j2, -j2, 5+j2, +5+j2, -5-j2
end;

function TComplexNumber.GetImaginaryPart: TNumberBase;
begin
  result := FImaginaryPart;
end;

function TComplexNumber.GetRealPart: TNumberBase;
begin
  result := FRealPart;
end;

procedure TComplexNumber.Invert;
var r,i: TNumberBase;
begin
  if not self.Valid then
  begin
    raise ECalculatingWithInvalidNumbers.Create('Inverting an invalid number');
  end;

  if IsAdditiveIdentity then
  begin
    raise EDivisionByZero.Create('Cannot invert an addition neutral element (additive identity)');
  end;

  r := FRealPart;
  i := FImaginaryPart;

  FRealPart := r / ( sqr(r)+sqr(i) );
  FImaginaryPart := ( -1*i ) / ( sqr(r)+sqr(i) );
end;

function TComplexNumber.IsAdditiveIdentity: boolean;
begin
  result := (FRealPart = 0) and (FImaginaryPart = 0);
end;

function TComplexNumber.IsMultiplicativeIdentity: boolean;
begin
  result := (FRealPart = 1) and (FImaginaryPart = 0);
end;

procedure TComplexNumber.MultiplyBy(ANumber: TNumber);
var
  currentRealPart: TNumberBase;
  currentImaginaryPart: TNumberBase;
begin
  if (not self.Valid) or (not ANumber.IsValid) then
  begin
    raise ECalculatingWithInvalidNumbers.Create('Calculating with invalid numbers');
  end;

  if ANumber is TComplexNumber then
  begin
    currentRealPart := FRealPart;
    currentImaginaryPart := FImaginaryPart;

    FRealPart := currentRealPart * TComplexNumber(ANumber).FRealPart - currentImaginaryPart * TComplexNumber(ANumber).FImaginaryPart;
    FImaginaryPart := currentRealPart * TComplexNumber(ANumber).FImaginaryPart + currentImaginaryPart * TComplexNumber(ANumber).FRealPart;
  end
  else begin
    raise EIncompatibleNumberTypes.Create(GetIncompatibleNumberTypesMsg(ANumber));
  end;
end;

procedure TComplexNumber.Random;
begin
  FRealPart := System.Random;
  FImaginaryPart := System.Random;
end;

class procedure TComplexNumber.SetFormat(AImaginaryCharacter: char;
  AImaginaryCharacterBeforeNumber: boolean);
begin
  imaginaryCharacter := AImaginaryCharacter;
  imaginaryCharacterBeforeNumber := AImaginaryCharacterBeforeNumber;
end;

procedure TComplexNumber.SetImaginaryPart(AValue: TNumberBase);
begin
  FImaginaryPart := AValue;
end;

procedure TComplexNumber.SetRealPart(AValue: TNumberBase);
begin
  FRealPart := AValue;
end;

procedure TComplexNumber.Subtract(ANumber: TNumber);
begin
  if (not self.Valid) or (not ANumber.IsValid) then
  begin
    raise ECalculatingWithInvalidNumbers.Create('Calculating with invalid numbers');
  end;

  if ANumber is TComplexNumber then
  begin
    FRealPart := FRealPart - TComplexNumber(ANumber).FRealPart;
    FImaginaryPart := FImaginaryPart - TComplexNumber(ANumber).ImaginaryPart;
  end
  else begin
    raise EIncompatibleNumberTypes.Create(GetIncompatibleNumberTypesMsg(ANumber));
  end;
end;

function TComplexNumber.ToExponentialString(const AFormat: String): String;
begin
  //TODO: implement me
end;

function TComplexNumber.ToString(const AFormat: String): String;
begin
  result := FormatFloat(AFormat, FRealPart);

  if FImaginaryPart <> 0 then
  begin
    if FImaginaryPart > 0 then
      result := result + '+'
    else
      result := result + '-';

    if imaginaryCharacterBeforeNumber then
      result := result + imaginaryCharacter;

    if System.Abs(FImaginaryPart) <> 1 then
      result := result + FormatFloat(AFormat, System.Abs(FImaginaryPart));

    if not imaginaryCharacterBeforeNumber then
      result := result + imaginaryCharacter;
  end;
end;

initialization

  TComplexNumber.SetFormat('i',false);

end.
